%locations
%error-verbose
%define api.pure
%name-prefix "bson"
%parse-param {yyscan_t scan}
%lex-param {yyscan_t scan}
%parse-param {buf_t *out}
%parse-param {buf_t *err}
%{
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include "macros.h"
#include "tag.h"
#include "buf.h"
#include "parser.h"
#include "lexer.h"

#define ASSERT(expr) do { int _ASSERT_; _ASSERT_ = (expr); assert(_ASSERT_); } while (0)
#define ENCODE_UINT8(u8) ASSERT(!buf_encode_uint8(out, (u8)))
#define ENCODE_UINT16(u16) ASSERT(!buf_encode_uint16(out, (u16)))
#define ENCODE_INT32(i32) ASSERT(!buf_encode_int32(out, (i32)))
#define ENCODE_DOUBLE(d) ASSERT(!buf_encode_double(out, (d)))
#define ENCODE_BINARY(p, l) ASSERT(!buf_encode_binary(out, (p), (l)))
	
	static void bsonerror(YYLTYPE *, yyscan_t *, buf_t *, buf_t *, char const *);
%}

%union {
	uint8_t uint8;
	uint16_t uint16;
	int32_t int32;
	double double_;
	struct {
		char const *bytes;
		size_t len;
	} string;
}

%token T_LEFT_BRACE "{"
%token T_RIGHT_BRACE "}"
%token T_COMMA ","
%token T_COLON ":"
%token T_LEFT_BRACKET "["
%token T_RIGHT_BRACKET "]"
%token <string> T_STRING "string"
%token <string> T_MODIFIED_STRING "modified string"
%token <uint8> T_UINT8 "byte"
%token <uint16> T_UINT16 "short"
%token <int32> T_INT32 "int"
%token <double_> T_DOUBLE "double"
%token T_TRUE "true"
%token T_FALSE "false"
%token T_NULL "null"
%token T_INVALID "token"
%token T_END 0 "end of string"

%start json

%%

json:
		value many_tokens;

many_tokens:
		/* empty */
	| some_tokens;

some_tokens:
		token
	| some_tokens token;

token:
		T_LEFT_BRACE
	| T_RIGHT_BRACE
	| T_COMMA
	| T_COLON
	| T_LEFT_BRACKET
	| T_RIGHT_BRACKET
	| T_STRING
	| T_MODIFIED_STRING
	| T_UINT8
	| T_UINT16
	| T_INT32
	| T_DOUBLE
	| T_TRUE
	| T_FALSE
	| T_NULL
	| T_INVALID;

object:
		T_LEFT_BRACE T_RIGHT_BRACE {
				ENCODE_UINT8(TAG_OBJECT);
				ENCODE_UINT8(TAG_END);
			}
	| T_LEFT_BRACE { ENCODE_UINT8(TAG_OBJECT); } members_maybe_trailing_comma T_RIGHT_BRACE { ENCODE_UINT8(TAG_END); }

members_maybe_trailing_comma:
		T_COMMA
	| members
	| members T_COMMA;
	
members:
		pair
	| members T_COMMA pair;

pair:
		string T_COLON value;

array:
		T_LEFT_BRACKET T_RIGHT_BRACKET {
				ENCODE_UINT8(TAG_ARRAY);
				ENCODE_UINT8(TAG_END);
			}
	| T_LEFT_BRACKET { ENCODE_UINT8(TAG_ARRAY); } elements_maybe_trailing_comma T_RIGHT_BRACKET { ENCODE_UINT8(TAG_END); };

elements_maybe_trailing_comma:
		T_COMMA
	| elements
	| elements T_COMMA;

elements:
		value
	| elements T_COMMA value;

value:
		string
	| T_UINT8 {
				ENCODE_UINT8(TAG_UINT8);
				ENCODE_UINT8($1);
			}
	| T_UINT16 {
				ENCODE_UINT8(TAG_UINT16);
				ENCODE_UINT16($1);
			}
	| T_INT32 {
				ENCODE_UINT8(TAG_INT32);
				ENCODE_INT32($1);
			}
	| T_DOUBLE {
				ENCODE_UINT8(TAG_DOUBLE);
				ENCODE_DOUBLE($1);
			}
	| object
	| array
	| T_TRUE { ENCODE_UINT8(TAG_TRUE); }
	| T_FALSE { ENCODE_UINT8(TAG_FALSE); }
	| T_NULL { ENCODE_UINT8(TAG_NULL); };

string:
		T_STRING {
				ENCODE_UINT8(TAG_STRING);
				ENCODE_INT32($1.len);
				ENCODE_BINARY($1.bytes, $1.len);
			}
	| T_MODIFIED_STRING {
				ENCODE_UINT8(TAG_MODIFIED_STRING);
				ENCODE_INT32($1.len);
				ENCODE_BINARY($1.bytes, $1.len);
			};

%%

static void
bsonerror(YYLTYPE * locp, unused yyscan_t * scan, unused buf_t * out, buf_t * err, char const * msg) {
	int first_column, last_column;
	
	if (buf_size(err) != 0) {
		buf_encode_literal(err, ", ");
	}
	first_column = locp->first_column;
	last_column = locp->last_column;
	if (first_column == last_column) {
		buf_encode_printf(err, "error at %d: %s", first_column, msg);
	} else {
		buf_encode_printf(err, "error at %d-%d: %s",
											first_column, last_column, msg);
	}
}
